package org.openstreetmap.gui.jmapviewer;

//License: GPL. Copyright 2008 by Jan Peter Stotz

import edu.cicese.alz.MapData;
import edu.cicese.alz.swing.SwingUtil;
import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle;
import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

/**
 * Provides a simple panel that displays pre-rendered map tiles loaded from the
 * OpenStreetMap project.
 *
 * @author Jan Peter Stotz
 */
public class MapViewer extends JPanel implements TileLoaderListener {
	private static final long serialVersionUID = 1L;

	// Vectors for clock-wise tile painting
	protected static final Point[] move = {new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1)};

	public static final int MAX_ZOOM = 22;
	public static final int MIN_ZOOM = 0;

	private MapData mapData;

	private LinkedList<MapMarker> mapMarkers;
	private LinkedList<MapTrack> mapTracks;
	private LinkedList<MapLocation> mapLocations;
	private MapHome mapHome;

	protected List<MapRectangle> mapRectangleList;

	protected boolean mapRectanglesVisible;

	protected Coordinate coordinate;

	public JSlider jsMarkers;
	protected JButton btnNextMarker;
	protected JButton btnPrevMarker;
	protected int markersShown;


	protected boolean tileGridVisible;

	protected TileController tileController;

	/**
	 * x- and y-position of the center of this map-panel on the world map
	 * denoted in screen pixel regarding the current zoom level.
	 */
	protected Point center;

	/**
	 * Current zoom level
	 */
	protected int zoom;

	protected JSlider jsZoom;
	protected JButton btnZoomIn;
	protected JButton btnZoomOut;

	private TileSource tileSource;

	// Attribution
	private Image attrImage;
	private String attrTermsUrl;
	public static final Font ATTR_FONT = new Font("Arial", Font.PLAIN, 10);
	public static final Font ATTR_LINK_FONT;

	private boolean painted = false;

	private boolean draw = true;

	static {
		HashMap<TextAttribute, Integer> aUnderline = new HashMap<TextAttribute, Integer>();
		aUnderline.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
		ATTR_LINK_FONT = ATTR_FONT.deriveFont(aUnderline);
	}

	/**
	 * Creates a standard {@link MapViewer} instance that can be controlled via
	 * mouse: hold right mouse button for moving, double click left mouse button
	 * or use mouse wheel for zooming. Loaded tiles are stored the
	 * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
	 * retrieving the tiles.
	 */
	public MapViewer(MapHome mapHome) {
		this(new MemoryTileCache());
		new DefaultMapController(this);
		this.mapHome = mapHome;
	}

	public MapViewer(MapData mapData) {
		this(new MemoryTileCache(), 4);

		this.mapData = mapData;
		this.mapHome = mapData.getMapHome();

		addMapMarker(mapData.getMapMarkers());
		addMapTrack(mapData.getMapTracks());
		addMapLocation(mapData.getMapLocations());

		markersShown = mapData.getMapMarkers().size();
		System.out.println(markersShown);
		initializeMarkerSlider();
		new DefaultMapController(this);
	}

	public MapViewer(TileCache tileCache, int downloadThreadCount) {
		super();
		tileSource = new OsmTileSource.Mapnik();
		tileController = new TileController(tileSource, tileCache, this);

		mapMarkers = new LinkedList<>();
		mapTracks = new LinkedList<>();
		mapLocations = new LinkedList<>();

		mapRectangleList = new LinkedList<>();
		mapRectanglesVisible = true;
		tileGridVisible = false;
		setLayout(null);
		initializeZoomSlider();
		setMinimumSize(new Dimension(tileSource.getTileSize(), tileSource.getTileSize()));
		setPreferredSize(new Dimension(400, 400));
		setDisplayPositionByLatLon(50, 9, 3);

		/*addMouseMotionListener(new MouseMotionListener() {
			@Override
			public void mouseDragged(MouseEvent e) {
			}

			@Override
			public void mouseMoved(MouseEvent e) {
				setCoordinate(e);
			}
		});*/
	}

	public MapViewer(TileCache tileCache) {
		super();
		this.mapData = new MapData();
		this.mapData.setHomeRadius(50);

		tileSource = new OsmTileSource.Mapnik();
		tileController = new TileController(tileSource, tileCache, this);

		mapRectangleList = new LinkedList<>();
		mapRectanglesVisible = true;
		setLayout(null);
		initializeZoomSlider();
		setZoomSliderBounds();
		setMinimumSize(new Dimension(tileSource.getTileSize(), tileSource.getTileSize()));
		setPreferredSize(new Dimension(400, 400));
		setDisplayPositionByLatLon(50, 9, 3);

		addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				if (SwingUtilities.isRightMouseButton(e)) {
					setCoordinate(e);
					mapHome = new MapHome(coordinate.getLat(), coordinate.getLon(), mapData.getHomeRadius());
					repaint();
				}
			}
		});
	}

	protected void setCoordinate(MouseEvent event) {
//		Point screenPoint = event.getLocationOnScreen();
//		coordinate = getPosition(screenPoint);
		coordinate = getPosition(event.getPoint());
	}

	public void setCoordinate(double lat, double lon) {
		coordinate = new Coordinate(lat, lon);
	}

	public Coordinate getCoordinate() {
		return coordinate;
	}

	protected void initializeMarkerSlider() {
		jsMarkers = SwingUtil.createSlider(JSlider.HORIZONTAL, 0, mapData.getMapMarkers().size(), 0, 80, 10, 600, 50);
		jsMarkers.setMajorTickSpacing(50);
		jsMarkers.setMinorTickSpacing(5);
		jsMarkers.setPaintTicks(true);
		jsMarkers.setPaintLabels(true);
		jsMarkers.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
//	            if (!jsMarkers.getValueIsAdjusting()) {
				setPoints(jsMarkers.getValue());
//	            }


				/*
								*
								* JSlider source = (JSlider)e.getSource();
						if (!source.getValueIsAdjusting()) {
							int fps = (int)source.getDate();
							if (fps == 0) {
								if (!frozen) stopAnimation();
							} else {
								delay = 1000 / fps;
								timer.setDelay(delay);
								timer.setInitialDelay(delay * 10);
								if (frozen) startAnimation();
							}
						}
						*/
			}
		});
		add(jsMarkers);

		ImageIcon iconNext = new ImageIcon(getClass().getClassLoader().getResource("icons/next.png"));
		btnNextMarker = SwingUtil.createButton("", "Show next marker", iconNext);
		btnNextMarker.setBounds(680, 10, 32, 32);
		btnNextMarker.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				addPoint();
			}
		});
		add(btnNextMarker);

		ImageIcon iconPrev = new ImageIcon(getClass().getClassLoader().getResource("icons/prev.png"));
		btnPrevMarker = SwingUtil.createButton("", "Show previous marker", iconPrev);
		btnPrevMarker.setBounds(48, 10, 32, 32);
		btnPrevMarker.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				removePoint();
			}
		});

		add(btnPrevMarker);

		pointsToShowChanged();
	}

	protected void initializeZoomSlider() {
		jsZoom = SwingUtil.createSlider(JSlider.VERTICAL, MIN_ZOOM, tileController.getTileSource().getMaxZoom(), 0, 20, 60, 30, 150);
		jsZoom.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				setZoom(jsZoom.getValue());
			}
		});
		add(jsZoom);

		ImageIcon iconIn = new ImageIcon(getClass().getClassLoader().getResource("icons/plus.png"));
		btnZoomIn = SwingUtil.createButton("", "Zoom in", iconIn);
		btnZoomIn.setBounds(14, 205, 16, 16);
		btnZoomIn.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				zoomIn();
			}
		});
		add(btnZoomIn);

		ImageIcon iconOut = new ImageIcon(getClass().getClassLoader().getResource("icons/minus.png"));
		btnZoomOut = SwingUtil.createButton("", "Zoom out", iconOut);
		btnZoomOut.setBounds(36, 205, 16, 16);
		btnZoomOut.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				zoomOut();
			}
		});
		add(btnZoomOut);
	}

	protected void setZoomSliderBounds() {
		jsZoom.setBounds(20, 20, 30, 150);
		btnZoomIn.setBounds(14, 165, 16, 16);
		btnZoomOut.setBounds(36, 165, 16, 16);
	}

	/**
	 * Changes the map pane so that it is centered on the specified coordinate
	 * at the given zoom level.
	 *
	 * @param lat  latitude of the specified coordinate
	 * @param lon  longitude of the specified coordinate
	 * @param zoom {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
	 */
	public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
		setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom);
	}

	/**
	 * Changes the map pane so that the specified coordinate at the given zoom
	 * level is displayed on the map at the screen coordinate
	 * <code>mapPoint</code>.
	 *
	 * @param mapPoint point on the map denoted in pixels where the coordinate should
	 *                 be set
	 * @param lat      latitude of the specified coordinate
	 * @param lon      longitude of the specified coordinate
	 * @param zoom     {@link #MIN_ZOOM} <= zoom level <=
	 *                 {@link TileSource#getMaxZoom()}
	 */
	public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
		int x = OsmMercator.LonToX(lon, zoom);
		int y = OsmMercator.LatToY(lat, zoom);
		setDisplayPosition(mapPoint, x, y, zoom);
	}

	public void setDisplayPosition(int x, int y, int zoom) {
		setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom);
	}

	public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
		if (zoom > tileController.getTileSource().getMaxZoom() || zoom < MIN_ZOOM)
			return;

		// Get the plain tile number
		Point p = new Point();
		p.x = x - mapPoint.x + getWidth() / 2;
		p.y = y - mapPoint.y + getHeight() / 2;
		center = p;
		setIgnoreRepaint(true);
		try {
			int oldZoom = this.zoom;
			this.zoom = zoom;
			if (oldZoom != zoom) {
				zoomChanged(oldZoom);
			}
			if (jsZoom.getValue() != zoom) {
				jsZoom.setValue(zoom);
			}
		} finally {
			setIgnoreRepaint(false);
			repaint();
		}
	}

	/**
	 * Sets the displayed map pane and zoom level so that all map markers are
	 * visible.
	 */
	public void setDisplayToFitMapMarkers() {
		if (mapMarkers == null || mapMarkers.size() == 0)
			return;
		int x_min = Integer.MAX_VALUE;
		int y_min = Integer.MAX_VALUE;
		int x_max = Integer.MIN_VALUE;
		int y_max = Integer.MIN_VALUE;
		int mapZoomMax = tileController.getTileSource().getMaxZoom();
		for (MapMarker marker : mapMarkers) {
			int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
			int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax);
			x_max = Math.max(x_max, x);
			y_max = Math.max(y_max, y);
			x_min = Math.min(x_min, x);
			y_min = Math.min(y_min, y);
		}
		int height = Math.max(0, getHeight());
		int width = Math.max(0, getWidth());
		// System.out.println(x_min + " < x < " + x_max);
		// System.out.println(y_min + " < y < " + y_max);
		// System.out.println("tiles: " + width + " " + height);
		int newZoom = mapZoomMax;
		int x = x_max - x_min;
		int y = y_max - y_min;
		while (x > width || y > height) {
			// System.out.println("zoom: " + zoom + " -> " + x + " " + y);
			newZoom--;
			x >>= 1;
			y >>= 1;
		}
		x = x_min + (x_max - x_min) / 2;
		y = y_min + (y_max - y_min) / 2;
		int z = 1 << (mapZoomMax - newZoom);
		x /= z;
		y /= z;
		setDisplayPosition(x, y, newZoom);
	}

	/**
	 * Sets the displayed map pane and zoom level so that all map markers are
	 * visible.
	 */
	public void setDisplayToFitMapRectangle() {
		if (mapRectangleList == null || mapRectangleList.size() == 0)
			return;
		int x_min = Integer.MAX_VALUE;
		int y_min = Integer.MAX_VALUE;
		int x_max = Integer.MIN_VALUE;
		int y_max = Integer.MIN_VALUE;
		int mapZoomMax = tileController.getTileSource().getMaxZoom();
		for (MapRectangle rectangle : mapRectangleList) {
			x_max = Math.max(x_max, OsmMercator.LonToX(rectangle.getBottomRight().getLon(), mapZoomMax));
			y_max = Math.max(y_max, OsmMercator.LatToY(rectangle.getTopLeft().getLat(), mapZoomMax));
			x_min = Math.min(x_min, OsmMercator.LonToX(rectangle.getTopLeft().getLon(), mapZoomMax));
			y_min = Math.min(y_min, OsmMercator.LatToY(rectangle.getBottomRight().getLat(), mapZoomMax));
		}
		int height = Math.max(0, getHeight());
		int width = Math.max(0, getWidth());
		// System.out.println(x_min + " < x < " + x_max);
		// System.out.println(y_min + " < y < " + y_max);
		// System.out.println("tiles: " + width + " " + height);
		int newZoom = mapZoomMax;
		int x = x_max - x_min;
		int y = y_max - y_min;
		while (x > width || y > height) {
			// System.out.println("zoom: " + zoom + " -> " + x + " " + y);
			newZoom--;
			x >>= 1;
			y >>= 1;
		}
		x = x_min + (x_max - x_min) / 2;
		y = y_min + (y_max - y_min) / 2;
		int z = 1 << (mapZoomMax - newZoom);
		x /= z;
		y /= z;
		setDisplayPosition(x, y, newZoom);
	}

	/**
	 * Calculates the latitude/longitude coordinate of the center of the
	 * currently displayed map area.
	 *
	 * @return latitude / longitude
	 */
	public Coordinate getPosition() {
		double lon = OsmMercator.XToLon(center.x, zoom);
		double lat = OsmMercator.YToLat(center.y, zoom);
		return new Coordinate(lat, lon);
	}

	/**
	 * Converts the relative pixel coordinate (regarding the top left corner of
	 * the displayed map) into a latitude / longitude coordinate
	 *
	 * @param mapPoint relative pixel coordinate regarding the top left corner of the
	 *                 displayed map
	 * @return latitude / longitude
	 */
	public Coordinate getPosition(Point mapPoint) {
		return getPosition(mapPoint.x, mapPoint.y);
	}

	/**
	 * Converts the relative pixel coordinate (regarding the top left corner of
	 * the displayed map) into a latitude / longitude coordinate
	 *
	 * @param mapPointX
	 * @param mapPointY
	 * @return
	 */
	public Coordinate getPosition(int mapPointX, int mapPointY) {
		int x = center.x + mapPointX - getWidth() / 2;
		int y = center.y + mapPointY - getHeight() / 2;
		double lon = OsmMercator.XToLon(x, zoom);
		double lat = OsmMercator.YToLat(y, zoom);
		return new Coordinate(lat, lon);
	}

	/**
	 * Calculates the position on the map of a given coordinate
	 *
	 * @param lat
	 * @param lon
	 * @param checkOutside
	 * @return point on the map or <code>null</code> if the point is not visible
	 *         and checkOutside set to <code>true</code>
	 */
	public Point getMapPosition(double lat, double lon, boolean checkOutside) {
		int x = OsmMercator.LonToX(lon, zoom);
		int y = OsmMercator.LatToY(lat, zoom);
		x -= center.x - getWidth() / 2;
		y -= center.y - getHeight() / 2;
		if (checkOutside) {
			if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
				return null;
		}
		return new Point(x, y);
	}

	/**
	 * Calculates the position on the map of a given coordinate
	 *
	 * @param lat
	 * @param lon
	 * @return point on the map or <code>null</code> if the point is not visible
	 */
	public Point getMapPosition(double lat, double lon) {
		return getMapPosition(lat, lon, true);
	}

	/**
	 * Calculates the position on the map of a given coordinate
	 *
	 * @param coord
	 * @return point on the map or <code>null</code> if the point is not visible
	 */
	public Point getMapPosition(Coordinate coord) {
		if (coord != null)
			return getMapPosition(coord.getLat(), coord.getLon());
		else
			return null;
	}

	/**
	 * Calculates the position on the map of a given coordinate
	 *
	 * @param coord
	 * @param checkOutside
	 * @return point on the map or <code>null</code> if the point is not visible
	 *         and checkOutside set to <code>true</code>
	 */
	public Point getMapPosition(Coordinate coord, boolean checkOutside) {
		if (coord != null)
			return getMapPosition(coord.getLat(), coord.getLon(), checkOutside);
		else
			return null;
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);

		setPainted(false);

		int iMove;

		int tileSize = tileSource.getTileSize();
		int tileX = center.x / tileSize;
		int tileY = center.y / tileSize;
		int off_x = (center.x % tileSize);
		int off_y = (center.y % tileSize);

		int w2 = getWidth() / 2;
		int h2 = getHeight() / 2;
		int posX = w2 - off_x;
		int posY = h2 - off_y;

		int diff_left = off_x;
		int diff_right = tileSize - off_x;
		int diff_top = off_y;
		int diff_bottom = tileSize - off_y;

		boolean start_left = diff_left < diff_right;
		boolean start_top = diff_top < diff_bottom;

		if (start_top) {
			if (start_left) {
				iMove = 2;
			} else {
				iMove = 3;
			}
		} else {
			if (start_left) {
				iMove = 1;
			} else {
				iMove = 0;
			}
		} // calculate the visibility borders
		int x_min = -tileSize;
		int y_min = -tileSize;
		int x_max = getWidth();
		int y_max = getHeight();

		// paint the tiles in a spiral, starting from center of the map
		boolean painted = true;
		int x = 0;
		while (painted) {
			painted = false;
			for (int i = 0; i < 4; i++) {
				if (i % 2 == 0) {
					x++;
				}
				for (int j = 0; j < x; j++) {
					if (x_min <= posX && posX <= x_max && y_min <= posY && posY <= y_max) {
						// tile is visible
						Tile tile = tileController.getTile(tileX, tileY, zoom);
						if (tile != null) {
							painted = true;
							tile.paint(g, posX, posY);
							if (mapData.isShowingGrid()) {
								g.drawRect(posX, posY, tileSize, tileSize);
							}
						}
					}
					Point p = move[iMove];
					posX += p.x * tileSize;
					posY += p.y * tileSize;
					tileX += p.x;
					tileY += p.y;
				}
				iMove = (iMove + 1) % move.length;
			}
		}
		// outer border of the map
		int mapSize = tileSize << zoom;
		g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);

		// g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);

		if (mapRectanglesVisible && mapRectangleList != null) {
			for (MapRectangle rectangle : mapRectangleList) {
				Coordinate topLeft = rectangle.getTopLeft();
				Coordinate bottomRight = rectangle.getBottomRight();
				if (topLeft != null && bottomRight != null) {
					Point pTopLeft = getMapPosition(topLeft.getLat(), topLeft.getLon(), false);
					Point pBottomRight = getMapPosition(bottomRight.getLat(), bottomRight.getLon(), false);
					if (pTopLeft != null && pBottomRight != null) {
						rectangle.paint(g, pTopLeft, pBottomRight);
					}
				}
			}
		}

		if (mapMarkers != null) {
			if (mapData.isShowingTracks() && mapMarkers.size() > 1) {
				for (MapTrack track : mapTracks) {
					paintTrack(g, track);
				}
			}
			if (mapData.isShowingMarkers() || mapData.isShowingLabels()) {
				for (MapMarker marker : mapMarkers) {
					paintMarker(g, marker);
				}
			}
		}

		if (mapData.isShowingLocations()) {
			for (MapLocation location : mapLocations) {
				if (!mapData.isShowingHome() || !location.isHome()) {
					paintLocation(g, location);
				}
			}
		}

		if (mapData.isShowingHome() && mapHome != null) {
			paintHome(g, mapHome);
		}


		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT));
		g2.setStroke(new BasicStroke());

//		if (draw) {
//			draw = false;
//			SwingUtilities.invokeLater(new Runnable() {
//				public void run() {
//					BufferedImage bi = new BufferedImage(MapViewer.this.getSize().width, MapViewer.this.getSize().height, BufferedImage.TYPE_INT_ARGB);
//					Graphics2D gAux = bi.createGraphics();
//					MapViewer.this.paint(gAux);
//					gAux.dispose();
//					try {
//						ImageIO.write(bi, "png", new File("D:\\_proyectoAlz\\tests\\" + System.currentTimeMillis() + ".png"));
//					} catch (IOException e) {
//						e.printStackTrace();
//					}
//				}
//			});
//
//		}

		paintAttribution(g);
	}

	// Paint a single marker.
	protected void paintMarker(Graphics g, MapMarker marker) {
		Point p = getMapPosition(marker.getLat(), marker.getLon());
		if (p != null) {
			marker.paint(g, p, mapData.isShowingMarkers(), mapData.isShowingLabels());
		}
	}

	// Paint a single line.
	protected void paintTrack(Graphics g, MapTrack track) {
		Point p1 = getMapPosition(track.getLat1(), track.getLon1());
		Point p2 = getMapPosition(track.getLat2(), track.getLon2());
		if (p1 != null && p2 != null) {
			track.paint(g, p1, p2);
		}
	}

	// Paint a single location.
	protected void paintLocation(Graphics g, MapLocation location) {
		Point p = getMapPosition(location.getLat(), location.getLon());
		Point p1 = null, p2 = null;
		if (!mapData.isAdjustLocationSize()) {
//			if (zoom > 15) {
//				p1 = getMapPosition(location.getMinLat(), location.getMinLon());
//				p2 = getMapPosition(location.getMaxLat(), location.getMaxLon());
//			} else {
				p1 = getMapPosition(location.getLat1(), location.getLon1());
				p2 = getMapPosition(location.getLat2(), location.getLon2());
//			}
		}
		else {
			p1 = getMapPosition(location.getMinLat(), location.getMinLon());
			p2 = getMapPosition(location.getMaxLat(), location.getMaxLon());
		}

//	    Point aux = getMapPosition(location.getLat() + 30 / 111111, location.getLon(), false);
//	    System.out.println(aux.x + " " + p.x + " " + aux.y + " " + p.y);
//	    System.out.println(aux.getX() + " " + p.getX() + " " + aux.getY() + " " + p.getY());
//
		if (p != null && p1 != null && p2 != null) {
			location.paint(g, p, p1, p2, zoom);
		}
	}

	// Paint home
	protected void paintHome(Graphics g, MapHome home) {
		Point p = getMapPosition(home.getLat(), home.getLon());
		Point p1 = getMapPosition(home.getLat1(), home.getLon1());
		Point p2 = getMapPosition(home.getLat2(), home.getLon2());
		if (p != null && p1 != null && p2 != null) {
			home.paint(g, p, p1, p2, zoom);
		}
	}

	/**
	 * Moves the visible map pane.
	 *
	 * @param x horizontal movement in pixel.
	 * @param y vertical movement in pixel
	 */
	public void moveMap(int x, int y) {
		center.x += x;
		center.y += y;
		repaint();
	}

	/**
	 * @return the current zoom level
	 */
	public int getZoom() {
		return zoom;
	}

	public int getMaxZoom() {
		return tileController.getTileSource().getMaxZoom();
	}

	public int getMinZoom() {
		return MIN_ZOOM;
	}

	/**
	 * Increases the current zoom level by one
	 */
	public void zoomIn() {
		setZoom(zoom + 1);
	}

	// Increases the current zoom level by one
	public void zoomIn(Point mapPoint) {
		setZoom(zoom + 1, mapPoint);
	}

	// Decreases the current zoom level by one
	public void zoomOut() {
		setZoom(zoom - 1);
	}

	// Decreases the current zoom level by one
	public void zoomOut(Point mapPoint) {
		setZoom(zoom - 1, mapPoint);
	}

	public void setZoom(int zoom, Point mapPoint) {
		if (zoom > tileController.getTileSource().getMaxZoom() || zoom < tileController.getTileSource().getMinZoom()
				|| zoom == this.zoom)
			return;
		Coordinate zoomPos = getPosition(mapPoint);
		tileController.cancelOutstandingJobs(); // Clearing outstanding load
		// requests
		setDisplayPositionByLatLon(mapPoint, zoomPos.getLat(), zoomPos.getLon(), zoom);
	}

	public void setZoom(int zoom) {
		setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
	}

	/**
	 * Every time the zoom level idxChanges this method is called. Override it in
	 * derived implementations for adapting zoom dependent values. The new zoom
	 * level can be obtained via {@link #getZoom()}.
	 *
	 * @param oldZoom the previous zoom level
	 */
	protected void zoomChanged(int oldZoom) {
		jsZoom.setToolTipText("Zoom level " + zoom);
		btnZoomIn.setToolTipText("Zoom to level " + (zoom + 1));
		btnZoomOut.setToolTipText("Zoom to level " + (zoom - 1));
		btnZoomOut.setEnabled(zoom > tileController.getTileSource().getMinZoom());
		btnZoomIn.setEnabled(zoom < tileController.getTileSource().getMaxZoom());
	}


	public boolean isTileGridVisible() {
		return tileGridVisible;
	}

	public void setTileGridVisible(boolean tileGridVisible) {
		this.tileGridVisible = tileGridVisible;
		repaint();
	}

	public void setShowingMarkers(boolean showingMarkers) {
		mapData.setShowingMarkers(showingMarkers);
		repaint();
	}

	public void setShowingLocations(boolean showingLocations) {
		mapData.setShowingLocations(showingLocations);
	}

	public void setShowingTracks(boolean showingTracks) {
		mapData.setShowingTracks(showingTracks);
	}

	public void setShowingLabels(boolean showingLabels) {
		mapData.setShowingLabels(showingLabels);
		repaint();
	}


	// Rectangles TODO check this
	public void setMapRectangleList(List<MapRectangle> mapRectangleList) {
		this.mapRectangleList = mapRectangleList;
		repaint();
	}

	public List<MapRectangle> getMapRectangleList() {
		return mapRectangleList;
	}


	// Add/Remove
	public void addMapMarker(List<MapMarker> mapMarkers) {
		this.mapMarkers.addAll(mapMarkers);
		repaint();
	}

	public void addMapMarker(MapMarker marker) {
		mapMarkers.add(marker);
		repaint();
	}

	public void removeMapMarker(MapMarker marker) {
		mapMarkers.remove(marker);
		repaint();
	}

	public void addMapTrack(List<MapTrack> mapTracks) {
		this.mapTracks.addAll(mapTracks);
		repaint();
	}

	public void addMapTrack(MapTrack track) {
		mapTracks.add(track);
		repaint();
	}

	public void removeMapTrack(MapTrack track) {
		mapTracks.remove(track);
		repaint();
	}

	public void addMapLocation(List<MapLocation> mapLocations) {
		this.mapLocations.addAll(mapLocations);
		repaint();
	}

	public void addMapLocation(MapLocation location) {
		mapLocations.add(location);
		repaint();
	}

	public void removeMapLocation(MapLocation location) {
		mapLocations.remove(location);
		repaint();
	}

	// Rectangles TODO check this
	public void addMapRectangle(MapRectangle rectangle) {
		mapRectangleList.add(rectangle);
		repaint();
	}

	public void removeMapRectangle(MapRectangle rectangle) {
		mapRectangleList.remove(rectangle);
		repaint();
	}


	public void setShowingMarkerControl(boolean showingMarkerControl) {
		mapData.setShowingMarkerControl(showingMarkerControl);
		jsMarkers.setVisible(showingMarkerControl);
		btnPrevMarker.setVisible(showingMarkerControl);
		btnNextMarker.setVisible(showingMarkerControl);
	}

	public void setShowingZoomControl(boolean showingZoomControl) {
		mapData.setShowingZoomControl(showingZoomControl);
		jsZoom.setVisible(showingZoomControl);
		btnZoomIn.setVisible(showingZoomControl);
		btnZoomOut.setVisible(showingZoomControl);
	}


	public void setTileSource(TileSource tileSource) {
		if (tileSource.getMaxZoom() > MAX_ZOOM)
			throw new RuntimeException("Maximum zoom level too high");
		if (tileSource.getMinZoom() < MIN_ZOOM)
			throw new RuntimeException("Minimum zoom level too low");
		this.tileSource = tileSource;
		tileController.setTileSource(tileSource);
		jsZoom.setMinimum(tileSource.getMinZoom());
		jsZoom.setMaximum(tileSource.getMaxZoom());
		tileController.cancelOutstandingJobs();
		if (zoom > tileSource.getMaxZoom()) {
			setZoom(tileSource.getMaxZoom());
		}
		attrImage = null;
		attrTermsUrl = null;
		/*boolean requireAttr = tileSource.requiresAttribution();
				if (requireAttr) {
					attrImage = tileSource.getAttributionImage();
					attrTermsUrl = tileSource.getTermsOfUseURL();
				} else {
					attrImage = null;
					attrTermsUrl = null;
				}*/
		repaint();
	}

	public void tileLoadingFinished(Tile tile, boolean success) {
		repaint();
	}

	public boolean isMapRectanglesVisible() {
		return mapRectanglesVisible;
	}

	/**
	 * Enables or disables painting of the {@link MapRectangle}
	 *
	 * @see #addMapRectangle(MapRectangle)
	 * @see #getMapRectangleList()
	 */
	public void setMapRectanglesVisible(boolean mapRectanglesVisible) {
		this.mapRectanglesVisible = mapRectanglesVisible;
		repaint();
	}

	/**
	 * (non-Javadoc)
	 *
	 * @see org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener#getTileCache
	 *      ()
	 */
	public TileCache getTileCache() {
		return tileController.getTileCache();
	}

	public void setTileLoader(TileLoader loader) {
		tileController.setTileLoader(loader);
	}

	private void paintAttribution(Graphics g) {
		if (!tileSource.requiresAttribution())
			return;
		// Draw attribution
		Font font = g.getFont();
		g.setFont(ATTR_LINK_FONT);

		Rectangle2D termsStringBounds = g.getFontMetrics().getStringBounds("Background Terms of Use", g);
		int textHeight = (int) termsStringBounds.getHeight() - 5;
		int termsTextY = getHeight() - textHeight;
		if (attrTermsUrl != null) {
			int x = 2;
			int y = getHeight() - textHeight;
			g.setColor(Color.black);
			g.drawString("Background Terms of Use", x + 1, y + 1);
			g.setColor(Color.white);
			g.drawString("Background Terms of Use", x, y);
		}

		// Draw attribution logo
		if (attrImage != null) {
			int x = 2;
			int height = attrImage.getHeight(null);
			int y = termsTextY - height - textHeight - 5;
			g.drawImage(attrImage, x, y, null);
		}

		g.setFont(ATTR_FONT);
		Coordinate topLeft = getPosition(0, 0);
		Coordinate bottomRight = getPosition(getWidth(), getHeight());
		String attributionText = tileSource.getAttributionText(zoom, topLeft, bottomRight);
		if (attributionText != null) {
			Rectangle2D stringBounds = g.getFontMetrics().getStringBounds(attributionText, g);
			int x = getWidth() - (int) stringBounds.getWidth();
			int y = getHeight() - textHeight;
			g.setColor(Color.black);
			g.drawString(attributionText, x + 1, y + 1);
			g.setColor(Color.white);
			g.drawString(attributionText, x, y);
		}

		g.setFont(font);
	}


	public void setPoints(int pointsToShow) {
		int diff = Math.abs(pointsToShow - markersShown);
		if (pointsToShow > markersShown) {
			for (; diff > 0; diff--) {
				addPoint();
			}
		} else if (pointsToShow < markersShown) {
			for (; diff > 0; diff--) {
				removePoint();
			}
		}
		pointsToShowChanged();
	}

	public void addPoint() {
		if (markersShown < mapData.getMapMarkers().size()) {
			addMapMarker(mapData.getMapMarkers().get(markersShown));
			markersShown++;
			if (markersShown > 1) {
				addMapTrack(mapData.getMapTracks().get(markersShown - 2));
			}
			pointsToShowChanged();
		}
	}

	public void removePoint() {
		if (markersShown > 0) {
			removeMapMarker(mapMarkers.get(markersShown - 1));
			markersShown--;
			if (markersShown != 0) {
				removeMapTrack(mapTracks.get(markersShown - 1));
			}
			pointsToShowChanged();
		}
	}

	protected void pointsToShowChanged() {
		jsMarkers.setToolTipText("Showing " + markersShown + " points.");
		jsMarkers.setValue(markersShown);
		btnPrevMarker.setEnabled(markersShown > 0);
		btnNextMarker.setEnabled(markersShown < mapData.getMapMarkers().size());
//		if (!mapLabelList.isEmpty()) {
//			lblInfo.setText(mapLabelList.get(mapLabelList.size() - 1).getLabelString());
//			txtTimestamp.setText(mapLabelList.get(mapLabelList.size() - 1).getTimestamp());
//		}
//		else {
//			lblInfo.setText("");
//			txtTimestamp.setText("");
//		}

//
//        jsZoom.setToolTipText("Zoom level " + zoom);
//        btnZoomIn.setToolTipText("Zoom to level " + (zoom + 1));
//        btnZoomOut.setToolTipText("Zoom to level " + (zoom - 1));
//        btnZoomOut.setEnabled(zoom > tileController.getTileSource().getMinZoom());
//        btnZoomIn.setEnabled(zoom < tileController.getTileSource().getMaxZoom());
	}


	protected void saveMap(String filename) {
		BufferedImage bi = new BufferedImage(this.getSize().width, this.getSize().height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = bi.createGraphics();
		this.paint(g2);
		g2.dispose();
		try {
			ImageIO.write(bi, "png", new File(filename + ".png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	public void saveMap() {
		BufferedImage bi = new BufferedImage(this.getSize().width, this.getSize().height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = bi.createGraphics();
		this.paint(g2);
		g2.dispose();
		try {
			ImageIO.write(bi, "png", new File("D:\\_proyectoAlz\\tests\\" + System.currentTimeMillis() + ".png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean isPainted() {
		return painted;
	}

	public void setPainted(boolean painted) {
		this.painted = painted;
	}


	public void setMapHome(MapHome mapHome) {
		this.mapHome = mapHome;
	}
}
